package com.cehome.cloudbox.utils;

import com.cehome.cloudbox.enums.ClockPool;

import java.net.InetAddress;
import java.util.concurrent.ThreadLocalRandom;

public class SolSnowflakeUtils {
    private static final long START_TIME = 1519740777809L;
    private static final long DATA_CENTER_ID_BITS = 2L;
    private static final long WORKER_ID_BITS = 8L;
    private static final long SEQUENCE_BITS = 12L;
    private static final long MAX_WORKER_ID = 255L;
    private static final long MAX_DATA_CENTER_ID = 3L;
    private static final long WORKER_ID_SHIFT = 12L;
    private static final long DATA_CENTER_ID_SHIFT = 20L;
    private static final long TIMESTAMP_LEFT_SHIFT = 22L;
    private static final long SEQUENCE_MASK = 4095L;
    private final long workerId;
    private final long dataCenterId;
    private long sequence;
    private long lastTimestamp;
    private static byte LAST_IP = 0;
    private final boolean clock;
    private final long timeOffset;
    private final boolean randomSequence;
    private final ThreadLocalRandom tlr;

    public SolSnowflakeUtils(long dataCenterId) {
        this(dataCenterId, (long)(255 & getLastIPAddress()), false, 5L, false);
    }

    public SolSnowflakeUtils(long dataCenterId, boolean clock, boolean randomSequence) {
        this(dataCenterId, (long)(255 & getLastIPAddress()), clock, 5L, randomSequence);
    }

    public SolSnowflakeUtils(long dataCenterId, long workerId, boolean clock, long timeOffset, boolean randomSequence) {
        this.sequence = 0L;
        this.lastTimestamp = -1L;
        this.tlr = ThreadLocalRandom.current();
        if (dataCenterId <= 3L && dataCenterId >= 0L) {
            if (workerId <= 255L && workerId >= 0L) {
                this.workerId = workerId;
                this.dataCenterId = dataCenterId;
                this.clock = clock;
                this.timeOffset = timeOffset;
                this.randomSequence = randomSequence;
            } else {
                throw new IllegalArgumentException("Worker Id can't be greater than 255 or less than 0");
            }
        } else {
            throw new IllegalArgumentException("Data Center Id can't be greater than 3 or less than 0");
        }
    }

    public synchronized Long nextId() {
        long currentTimestamp = this.timeGen();
        long offset;
        if (currentTimestamp < this.lastTimestamp) {
            offset = this.lastTimestamp - currentTimestamp;
            if (offset > this.timeOffset) {
                throw new RuntimeException("Clock moved backwards, refusing to generate id for [" + offset + "ms]");
            }

            try {
                this.wait(offset << 1);
            } catch (Exception var6) {
                throw new RuntimeException(var6);
            }

            currentTimestamp = this.timeGen();
            if (currentTimestamp < this.lastTimestamp) {
                throw new RuntimeException("Clock moved backwards, refusing to generate id for [" + offset + "ms]");
            }
        }

        if (this.lastTimestamp == currentTimestamp) {
            offset = this.sequence + 1L;
            if (this.randomSequence && offset > 4095L) {
                offset %= 4095L;
            }

            this.sequence = offset & 4095L;
            if (this.sequence == 0L) {
                currentTimestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            this.sequence = this.randomSequence ? this.tlr.nextLong(4096L) : 0L;
        }

        this.lastTimestamp = currentTimestamp;
        offset = currentTimestamp - 1519740777809L;
        return offset << 22 | this.dataCenterId << 20 | this.workerId << 12 | this.sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp;
        for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
        }

        return timestamp;
    }

    private long timeGen() {
        return this.clock ? ClockPool.INSTANCE.currentTimeMillis() : System.currentTimeMillis();
    }

    private static byte getLastIPAddress() {
        if (LAST_IP != 0) {
            return LAST_IP;
        } else {
            try {
                InetAddress inetAddress = InetAddress.getLocalHost();
                byte[] addressByte = inetAddress.getAddress();
                LAST_IP = addressByte[addressByte.length - 1];
            } catch (Exception var2) {
                throw new RuntimeException("Unknown Host Exception", var2);
            }

            return LAST_IP;
        }
    }
}